﻿// TC Core Library
// Copyright © 2008-2010 Tommy Carlier
// http://tc.codeplex.com
// License: Microsoft Public License (Ms-PL): http://tc.codeplex.com/license

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace TC
{
	/// <summary>Represents the result of an asynchronous operation.</summary>
	public interface IResult
	{
		/// <summary>Gets the exception that occurred while performing the asynchronous operation.</summary>
		/// <value>The exception that occurred while performing the asynchronous operation, or null if the operation succeeded.</value>
		Exception Exception { get; }
	}

	/// <summary>Represents the result of an asynchronous operation that produces a value.</summary>
	/// <typeparam name="T">The type of the value that was produced by the operation.</typeparam>
	public interface IResult<T> : IResult
	{
		/// <summary>Gets the produced value.</summary>
		/// <value>The value that was produced by the operation.</value>
		T Value { get; }
	}

	/// <summary>Encapsulates a method that handles the result of an asynchronous operation.</summary>
	/// <param name="result">The result of the asynchronous operation.</param>
	public delegate void ResultHandler(IResult result);

	/// <summary>Encapsulates a method that handles the result of an asynchronous operation.</summary>
	/// <typeparam name="T">The type of the value of the specified <see cref="T:IResult{T}"/>.</typeparam>
	/// <param name="result">The result of the asynchronous operation.</param>
	public delegate void ResultHandler<T>(IResult<T> result);

	/// <summary>Provides functions to create instances of <see cref="T:IResult"/> and <see cref="T:IResult{T}"/> for
	/// succeeded and failed asynchronous operations, and some extension methods on the result interfaces.</summary>
	public static class Result
	{
		private static readonly IResult
			_succeededUnitResult = new SucceededResult<Unit>(Unit.Instance);

		#region CreateSucceeded, CreateFailed

		/// <summary>Creates an instance of <see cref="T:IResult"/> for a successful asynchronous operation.</summary>
		/// <returns>The created <see cref="T:IResult"/>.</returns>
		public static IResult CreateSucceeded()
		{
			return _succeededUnitResult;
		}

		/// <summary>Creates an instance of <see cref="T:IResult{T}"/> for a successful asynchronous operation.</summary>
		/// <typeparam name="T">The type of the value produced by the operation.</typeparam>
		/// <param name="value">The value produced by the operation.</param>
		/// <returns>The created <see cref="T:IResult{T}"/>.</returns>
		public static IResult<T> CreateSucceeded<T>(T value)
		{
			return new SucceededResult<T>(value);
		}

		/// <summary>Creates an instance of <see cref="T:IResult"/> for a failed asynchronous operation.</summary>
		/// <param name="exception">The <see cref="T:Exception"/> that occurred.</param>
		/// <returns>The created <see cref="T:IResult"/>.</returns>
		public static IResult CreateFailed(Exception exception)
		{
			if (exception == null) throw new ArgumentNullException("exception");
			return new FailedResult<Unit>(exception);
		}

		/// <summary>Creates an instance of <see cref="T:IResult"/> for a failed asynchronous operation.</summary>
		/// <typeparam name="T">The type of the value that might have been produced by the operation.</typeparam>
		/// <param name="exception">The <see cref="T:Exception"/> that occurred.</param>
		/// <returns>The created <see cref="T:IResult"/>.</returns>
		[SuppressMessage(
			"Microsoft.Design",
			"CA1004:GenericMethodsShouldProvideTypeParameter",
			Justification = "The type T is a necessary part of the returned result, but failed results don't have a value.")]
		public static IResult<T> CreateFailed<T>(Exception exception)
		{
			if (exception == null) throw new ArgumentNullException("exception");
			return new FailedResult<T>(exception);
		}

		#endregion

		#region HasSucceeded, HasFailed

		/// <summary>Determines whether the operation that produced the specified result has succeeded.</summary>
		/// <param name="result">The <see cref="T:IResult"/> of the operation.</param>
		/// <returns>If the operation has succeeded, <c>true</c>; otherwise, <c>false</c>.</returns>
		public static bool HasSucceeded(this IResult result)
		{
			if (result == null) throw new ArgumentNullException("result");
			return result.Exception == null;
		}

		/// <summary>Determines whether the operation that produced the specified result has failed.</summary>
		/// <param name="result">The <see cref="T:IResult"/> of the operation.</param>
		/// <returns>If the operation has failed, <c>true</c>; otherwise, <c>false</c>.</returns>
		public static bool HasFailed(this IResult result)
		{
			if (result == null) throw new ArgumentNullException("result");
			return result.Exception != null;
		}

		#endregion

		#region Bind

		/// <summary>Binds <paramref name="result"/> to <paramref name="resultHandler"/>.</summary>
		/// <param name="resultHandler">The <see cref="T:ResultHandler"/> to bind <paramref name="result"/> to.</param>
		/// <param name="result">The <see cref="T:IResult"/> to bind to <paramref name="resultHandler"/>.</param>
		/// <returns>The <see cref="T:Action"/> that, when called, will call <paramref name="resultHandler"/>
		/// with <paramref name="result"/> as the argument.</returns>
		public static Action Bind(this ResultHandler resultHandler, IResult result)
		{
			if (resultHandler == null) throw new ArgumentNullException("resultHandler");
			return BindCore(resultHandler, result);
		}

		private static Action BindCore(ResultHandler resultHandler, IResult result)
		{
			return () => resultHandler(result);
		}

		/// <summary>Binds <paramref name="result"/> to <paramref name="resultHandler"/>.</summary>
		/// <typeparam name="T">The type of the value of the result.</typeparam>
		/// <param name="resultHandler">The <see cref="T:ResultHandler{T}"/> to bind <paramref name="result"/> to.</param>
		/// <param name="result">The <see cref="T:IResult{T}"/> to bind to <paramref name="resultHandler"/>.</param>
		/// <returns>The <see cref="T:Action"/> that, when called, will call <paramref name="resultHandler"/>
		/// with <paramref name="result"/> as the argument.</returns>
		public static Action Bind<T>(this ResultHandler<T> resultHandler, IResult<T> result)
		{
			if (resultHandler == null) throw new ArgumentNullException("resultHandler");
			return BindCore(resultHandler, result);
		}

		private static Action BindCore<T>(ResultHandler<T> resultHandler, IResult<T> result)
		{
			return () => resultHandler(result);
		}

		#endregion

		#region inner class SucceededResult

		private sealed class SucceededResult<T> : IResult<T>
		{
			private readonly T _value;

			internal SucceededResult(T value)
			{
				_value = value;
			}

			T IResult<T>.Value
			{
				get { return _value; }
			}

			Exception IResult.Exception
			{
				get { return null; }
			}

			public override string ToString()
			{
				return "Success: {0}".FormatInvariant(_value);
			}
		}

		#endregion

		#region inner class FailedResult

		private sealed class FailedResult<T> : IResult<T>
		{
			private readonly Exception _exception;

			internal FailedResult(Exception exception)
			{
				_exception = exception;
			}

			T IResult<T>.Value
			{
				get { throw new InvalidOperationException(); }
			}

			Exception IResult.Exception
			{
				get { return _exception; }
			}

			public override string ToString()
			{
				return "Failed: " + (_exception.Message ?? _exception.ToString());
			}
		}

		#endregion
	}
}
